﻿package extremefx.collections {
	import extremefx.conditions.ensures;	
	import extremefx.conditions.requires;	
	import extremefx.conditions.requiresString;	
	
	import flash.net.registerClassAlias;
	import flash.utils.IDataInput;
	import flash.utils.IDataOutput;
	import flash.utils.Dictionary;
	
	import extremefx.ICloneable;
	import extremefx.IDisposable;
	import extremefx.IEnumerator;

	/**
	 * @author Marcelo Volmaro
	 */
	public final class MirrorMap implements IDictionary, ICloneable, IDisposable {
		registerClassAlias("extremefx.collections.MirrorMap", MirrorMap);
		
		private var _entries:flash.utils.Dictionary;
		private var _reverseEntries:flash.utils.Dictionary;
		private var _count:uint;
		private var _dirty:Boolean;
		private var _keys:Vector.<String>;
		
		public function MirrorMap(pDic:IDictionary = null) {
			clear();
			
			if (pDic){
				var e:IEnumerator = getEnumerator();
				var c:KeyValuePair;
				
				while(e.moveNext()) {
					c = KeyValuePair(e.current);
					
					//no null entries, no duplication allowed
					if (!c.key || !c.value || contains(c.key) || containsValue(c.value)) continue;
					add(c.key, c.value);
				}
			}
		}
		
		public function add(pKey:String, pValue:*):void {
			requiresString(pKey, "key").isNotNullOrEmpty();
			requires(pValue, "value").isNotNull();
			
			ensures(_reverseEntries[pValue], "Duplicated value entry:"+pValue).isNull();
			ensures(_entries[pKey], "Duplicated key entry:"+pKey).isNull();
			
			_entries[pKey] = pValue;
			_reverseEntries[pValue] = pKey;
			++_count;
			_dirty = true;
		}
		
		public function clear():void {
			_entries = new flash.utils.Dictionary();
			_reverseEntries = new flash.utils.Dictionary();
			_count = 0;
			_dirty = false;
			_keys = new Vector.<String>();
		}
		
		public function contains(pKey:String):Boolean {
			requiresString(pKey, "key").isNotNullOrEmpty();
			return _entries[pKey] != null;
		}
		
		public function containsValue(pValue:*):Boolean {
			requires(pValue, "value").isNotNull();
			return _reverseEntries[pValue] != null;
		}
		
		public function remove(pKey:String):void {
			requiresString(pKey, "key").isNotNullOrEmpty();
			
			if (_entries[pKey]){
				var val:* = _entries[pKey]; 
				_entries[pKey] = _reverseEntries[val] = null;
				delete _entries[pKey]; 
				delete _reverseEntries[val];
				_count--;
				_dirty = true;
			}
		}
		
		public function removeValue(pValue:*):void {
			var name:String;// = _reverseEntries[pValue];
			if ((name = _reverseEntries[pValue])){
				_entries[name] = _reverseEntries[pValue] = null;
				delete _entries[name]; 
				delete _reverseEntries[pValue];
				_count--;
				_dirty = true;
			}
		}
		
		public function replaceKey(pOldKey:String, pNewKey:String):void {
			requiresString(pOldKey, "oldKey").isNotNullOrEmpty();
			requiresString(pNewKey, "newKey").isNotNullOrEmpty();
			
			ensures(_entries[pOldKey]).isNotNull("Key ["+pOldKey+"] does not exists.");

			var val:* = _entries[pOldKey];
			_entries[pOldKey] = null;
			delete _entries[pOldKey];
			
			_entries[pNewKey] = val;
			_reverseEntries[val] = pNewKey;
			_dirty = true;
		}
		
		public function replaceValue(pKey:String, pValue:*):void {
			requiresString(pKey, "key").isNotNullOrEmpty();
			requires(pValue, "value").isNotNull();
			
			ensures(_entries[pKey]).isNotNull("Key ["+pKey+"] does not exists.");
			
			var val:* = _entries[pKey];
			_reverseEntries[val] = null;
			delete _reverseEntries[val];
			
			_entries[pKey] = pValue;
			_reverseEntries[pValue] = pKey;
		}
		
		public function getValue(pKey:String):*{
			requiresString(pKey, "key").isNotNullOrEmpty();
			return _entries[pKey];
		}
		
		public function getKey(pValue:*):String{
			requires(pValue, "value").isNotNull();
			return _reverseEntries[pValue];
		}
		
		public function toArray():Array {
			var a:Array = new Array();
			
			for (var key:String in _entries){
				a.push(new KeyValuePair(key, _entries[key]));
			}
			
			return a;
		}
		
		public function getEnumerator():IEnumerator {
			return new MapEnumerator(keys, _entries);
		}
		
		public function readExternal(input:IDataInput):void {
			_entries = input.readObject();
			_reverseEntries = input.readObject();
			_count = input.readUnsignedInt();
		}
		
		public function writeExternal(output:IDataOutput):void {
			output.writeObject(_entries);
			output.writeObject(_reverseEntries);
			output.writeUnsignedInt(_count);
		}
		
		public function get keys():Vector.<String> {
			if (_dirty){
				var keys:Vector.<String> = new Vector.<String>();
				for (var i:String in _entries){
					keys.push(i);
				}
				
				_keys = keys;
				_dirty = false;
			}
			
			return _keys;
		}
		
		public function get values():Array {
			var values:Array = [];
			for each(var i:* in _entries){
				values.push(i);
			}
			
			return values;
		}
		
		public function get count():uint {
			return _count;
		}
		
		public function dispose():void {
			_entries = _reverseEntries = null;
			_keys = null;
			_count = 0;
		}
		
		public function clone():Object {
			return new MirrorMap(this);
		}
	}
}

import extremefx.IEnumerator;
import extremefx.collections.KeyValuePair;
import flash.utils.Dictionary;

final class MapEnumerator implements IEnumerator {
	private var _dict:flash.utils.Dictionary;
	private var _keys:Vector.<String>;
	private var _index:int;
	private var _count:uint;
	
	public function MapEnumerator(pKeys:Vector.<String>, pDict:flash.utils.Dictionary) {
		_dict = pDict;
		_index = -1;
		_count = pKeys.length;
		_keys = pKeys;
	}

	public function moveNext():Boolean {
		++_index;
		return (_index < _count);
	}
	
	public function reset():void {
		_index = -1;
	}
	
	public function get current():* {
		var key:String;
		return new KeyValuePair(key = _keys[_index], _dict[key]);
	}
}